﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Web;

namespace AliPayApi
{
	public class AliPayApi
	{
		private string alipay_https_veryfy_url = "https://mapi.alipay.com/gateway.do?service=notify_verify&";


		/// <summary>
		///  验证消息是否是支付宝发出的合法消息
		/// </summary>
		/// <param name="inputPara">通知返回参数数组</param>
		/// <param name="notify_id">通知验证ID</param>
		/// <param name="sign">支付宝生成的签名结果</param>
		/// <returns>验证结果</returns>
		public bool Verify(IDictionary<string, string> config, SortedDictionary<string, string> array, string notifyId, string sign)
		{
			//获取返回时的签名验证结果
			bool isSign = GetSignVeryfy(config, array, sign);

			//获取是否是支付宝服务器发来的请求的验证结果
			string responseTxt = "false";
			if (notifyId != null && notifyId != "") { responseTxt = GetResponseTxt(config, notifyId); }

			//写日志记录（若要调试，请取消下面两行注释）
			//string sWord = "responseTxt=" + responseTxt + "\n isSign=" + isSign.ToString() + "\n 返回回来的参数：" + GetPreSignStr(inputPara) + "\n ";
			//Core.LogResult(sWord);

			//判断responsetTxt是否为true，isSign是否为true
			//responsetTxt的结果不是true，与服务器设置问题、合作身份者ID、notify_id一分钟失效有关
			//isSign不是true，与安全校验码、请求时的参数格式（如：带自定义参数等）、编码格式有关
			if (responseTxt == "true" && isSign)//验证成功
			{
				return true;
			}
			else//验证失败
			{
				return false;
			}
		}

		/// <summary>
		/// 获取返回时的签名验证结果
		/// </summary>
		/// <param name="inputPara">通知返回参数数组</param>
		/// <param name="sign">对比的签名结果</param>
		/// <returns>签名验证结果</returns>
		public bool GetSignVeryfy(IDictionary<string, string> config, SortedDictionary<string, string> array, string sign)
		{
			Dictionary<string, string> sPara = new Dictionary<string, string>();

			//过滤空值、sign与sign_type参数
			sPara = FilterPara(array);

			//获取待签名字符串
			string preSignStr = CreateLinkString(sPara);

			//获得签名验证结果
			bool isSign = false;
			if (sign != null && sign != "")
			{
				switch (config["SignType"])
				{
					case "RSA":
						isSign = RSA.verify(preSignStr, sign, config["PublicKey"], config["Charset"]);

						//debug
						/*
						File.AppendAllText(HttpContext.Current.Server.MapPath("~/log.txt"), "\r\n==========================  start: 签名验证结果  =====================\r\n" +
																		"preSignStr：" + preSignStr + "\r\n" +
																		"sign：" + sign + "\r\n" +
																		"PublicKey：" + config["PublicKey"] + "\r\n" +
																		"Charset：" + config["Charset"] + "\r\n" +
																		 "\r\n===========================  end ===============================\r\n");
						*/
						break;
					case "MD5":
						isSign = sign == Sign(preSignStr, config["Key"], config["SignType"], config["Charset"]);
						break;
					default:
						break;
				}
			}

			return isSign;
		}

		/*
		/// <summary>
		///  验证消息是否是支付宝发出的合法消息
		/// </summary>
		/// <param name="inputPara">通知返回参数数组</param>
		/// <param name="notify_id">通知验证ID</param>
		/// <param name="sign">支付宝生成的签名结果</param>
		/// <returns>验证结果</returns>
		public bool Verify(IDictionary<string, string> config, SortedDictionary<string, string> array, string notifyID, string sign)
		{
			//获取返回回来的待签名数组签名后结果
			string mysign = GetResponseMysign(config, array);
			//获取是否是支付宝服务器发来的请求的验证结果
			string responseTxt = "true";
			if (notifyID != "") { responseTxt = GetResponseTxt(config, notifyID); }

			//写日志记录（若要调试，请取消下面两行注释）
			//string sWord = "responseTxt=" + responseTxt + "\n sign=" + sign + "&mysign=" + mysign + "\n 返回回来的参数：" + GetPreSignStr(inputPara) + "\n ";
			//Core.LogResult(sWord);

			//验证
			//responsetTxt的结果不是true，与服务器设置问题、合作身份者ID、notify_id一分钟失效有关
			//mysign与sign不等，与安全校验码、请求时的参数格式（如：带自定义参数等）、编码格式有关
			if (responseTxt == "true" && sign == mysign)//验证成功
			{
				return true;
			}
			else//验证失败
			{
				return false;
			}
		}
		*/

		/*
		/// <summary>
		/// 获取返回回来的待签名数组签名后结果
		/// </summary>
		/// <param name="inputPara">通知返回参数数组</param>
		/// <returns>签名结果字符串</returns>
		private string GetResponseMysign(IDictionary<string, string> config, SortedDictionary<string, string> inputPara)
		{
			Dictionary<string, string> sPara = new Dictionary<string, string>();

			//过滤空值、sign与sign_type参数
			sPara = FilterPara(inputPara);

			//获得签名结果
			string mysign = BuildMysign(sPara, config["Key"], config["SignType"], config["Charset"]);

			return mysign;
		}
		*/

		/// <summary>
		/// 获取是否是支付宝服务器发来的请求的验证结果
		/// </summary>
		/// <param name="notify_id">通知验证ID</param>
		/// <returns>验证结果</returns>
		private string GetResponseTxt(IDictionary<string, string> config, string notify_id)
		{
			string veryfy_url = alipay_https_veryfy_url + "partner=" + config["Partner"] + "&notify_id=" + notify_id;

			//获取远程服务器ATN结果，验证是否是支付宝服务器发来的请求
			string responseTxt = GetHttp(veryfy_url, 120000);

			return responseTxt;
		}

		/// <summary>
		/// 获取远程服务器ATN结果
		/// </summary>
		/// <param name="strUrl">指定URL路径地址</param>
		/// <param name="timeout">超时时间设置</param>
		/// <returns>服务器ATN结果</returns>
		private string GetHttp(string strUrl, int timeout)
		{
			string strResult;
			try
			{
				HttpWebRequest myReq = (HttpWebRequest)HttpWebRequest.Create(strUrl);
				myReq.Timeout = timeout;
				HttpWebResponse HttpWResp = (HttpWebResponse)myReq.GetResponse();
				Stream myStream = HttpWResp.GetResponseStream();
				StreamReader sr = new StreamReader(myStream, Encoding.Default);
				StringBuilder strBuilder = new StringBuilder();
				while (-1 != sr.Peek())
				{
					strBuilder.Append(sr.ReadLine());
				}

				strResult = strBuilder.ToString();
			}
			catch (Exception exp)
			{
				strResult = "Error：" + exp.Message;
			}

			return strResult;
		}

		/// <summary>
		/// 生成签名结果
		/// </summary>
		/// <param name="sArray">要签名的数组</param>
		/// <param name="key">安全校验码</param>
		/// <param name="sign_type">签名类型</param>
		/// <param name="_input_charset">编码格式</param>
		/// <returns>签名结果字符串</returns>
		public string BuildMysign(IDictionary<string, string> array, string key, string signType, string charset)
		{
			//把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
			string prestr = CreateLinkString(array);

			//把最终的字符串签名，获得签名结果
			return Sign(prestr, key, signType, charset);
		}

		/// <summary>
		/// 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
		/// </summary>
		/// <param name="sArray">需要拼接的数组</param>
		/// <returns>拼接完成以后的字符串</returns>
		private string CreateLinkString(IDictionary<string, string> dicArray)
		{
			StringBuilder prestr = new StringBuilder();
			foreach (KeyValuePair<string, string> temp in dicArray)
			{
				prestr.Append(temp.Key + "=" + temp.Value + "&");
			}

			//去掉最後一個&字符
			int nLen = prestr.Length;
			prestr.Remove(nLen - 1, 1);

			return prestr.ToString();
		}

		/// <summary>
		/// 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串，并对参数值做urlencode
		/// </summary>
		/// <param name="sArray">需要拼接的数组</param>
		/// <param name="code">字符编码</param>
		/// <returns>拼接完成以后的字符串</returns>
		public string CreateLinkStringUrlencode(IDictionary<string, string> dicArray, Encoding code)
		{
			StringBuilder prestr = new StringBuilder();
			foreach (KeyValuePair<string, string> temp in dicArray)
			{
				prestr.Append(temp.Key + "=" + HttpUtility.UrlEncode(temp.Value, code) + "&");
			}

			//去掉最後一個&字符
			int nLen = prestr.Length;
			prestr.Remove(nLen - 1, 1);

			return prestr.ToString();
		}

		/// <summary>
		/// 签名字符串
		/// </summary>
		/// <param name="prestr">需要签名的字符串</param>
		/// <param name="sign_type">签名类型</param>
		/// <param name="_input_charset">编码格式</param>
		/// <returns>签名结果</returns>
		private string Sign(string prestr, string key, string sign_type, string _input_charset)
		{
			if (sign_type.ToUpper() == "RSA")
			{
				return RSA.sign(prestr, key, _input_charset);
			}
			else if (sign_type.ToUpper() == "MD5")
			{
				//把拼接后的字符串再与安全校验码直接连接起来
				prestr = prestr + key;

				StringBuilder sb = new StringBuilder(32);
				MD5 md5 = new MD5CryptoServiceProvider();
				byte[] t = md5.ComputeHash(Encoding.GetEncoding(_input_charset).GetBytes(prestr));
				for (int i = 0; i < t.Length; i++)
				{
					sb.Append(t[i].ToString("x").PadLeft(2, '0'));
				}
				return sb.ToString();
			}

			return null;
		}

		/// <summary>
		/// 除去数组中的空值和签名参数并以字母a到z的顺序排序
		/// </summary>
		/// <param name="dicArrayPre">过滤前的参数组</param>
		/// <returns>过滤后的参数组</returns>
		private Dictionary<string, string> FilterPara(SortedDictionary<string, string> dicArrayPre)
		{
			Dictionary<string, string> dicArray = new Dictionary<string, string>();
			foreach (KeyValuePair<string, string> temp in dicArrayPre)
			{
				if (temp.Key.ToLower() != "sign" && temp.Key.ToLower() != "sign_type" && temp.Value != "" && temp.Value != null)
				{
					dicArray.Add(temp.Key.ToLower(), temp.Value);
				}
			}

			return dicArray;
		}

		/// <summary>
		/// 获取支付宝通知消息，并以“参数名=参数值”的形式组成数组
		/// </summary>
		/// <returns>request回来的信息组成的数组</returns>
		public SortedDictionary<string, string> GetHttpString()
		{
			SortedDictionary<string, string> result = new SortedDictionary<string, string>();
			HttpRequest request = HttpContext.Current.Request;
			NameValueCollection coll = request.HttpMethod == "POST" ? request.Form : request.QueryString;

			foreach (string key in coll.AllKeys)
			{
				result.Add(key, coll[key]);
			}

			return result;
		}
	}
}
